﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace IntelliLance.Math.Parser
{
    internal static class ExpressionVisitor
    {
        internal static ParameterExpression Variable(VariableToken token)
        {
            return Expression.Variable(token.Type, token.Value.ToString());
        }

        internal static ConstantExpression Constant(ConstantToken token)
        {
            return Expression.Constant(token.Value, token.Type);
        }

        internal static Expression Call(FunctionToken token, IList<Expression> parameters)
        {
            if (token.Expression != null)
            {
                parameters = VerifyParametersTypes(parameters, token.Expression);
                return Expression.Invoke(token.Expression, parameters);
            }
            else
            {
                parameters = VerifyParametersTypes(parameters, token.Function);
                return Expression.Call(null, token.Function, parameters);
            }
        }

        private static IList<Expression> VerifyParametersTypes(IList<Expression> parameters, Expression expression)
        {
            if (expression == null)
                return parameters;
            return new ParametersVisitor(parameters).GetParameters(expression);
        }

        private static IList<Expression> VerifyParametersTypes(IList<Expression> parameters, MethodInfo method)
        {
            if (method == null)
                return parameters;

            List<Expression> newParams = new List<Expression>();
            foreach (var item in method.GetParameters()
                .Zip(parameters, (mp, p) => new { mp, p }))
            {
                if (item.p.Type != item.mp.ParameterType)
                {
                    var param = Expression.Convert(item.p, item.mp.ParameterType);
                    newParams.Add(param);
                }
                else
                    newParams.Add(item.p);
            }

            return newParams;
        }


        private static void Visit(ref Expression left, ref Expression right)
        {
            var leftTypeCode = Type.GetTypeCode(left.Type);
            var rightTypeCode = Type.GetTypeCode(right.Type);

            if (leftTypeCode == rightTypeCode)
                return;

            if (leftTypeCode > rightTypeCode)
                right = Expression.Convert(right, left.Type);
            else
                left = Expression.Convert(left, right.Type);
        }

        internal static Expression BinaryExpression(OperatorToken o, Expression right, Expression left)
        {
            Expression opCall = null;
            Visit(ref left, ref right);
            switch (o.Value.ToString())
            {
                case "+":
                    opCall = Expression.Add(left, right); break;

                case "-":
                    opCall = Expression.Subtract(left, right); break;

                case "*":
                    opCall = Expression.Multiply(left, right); break;

                case "/":
                    opCall = Expression.Divide(left, right); break;

                case "=":
                    opCall = Expression.Assign(left, right); break;

                case "%":
                    opCall = Expression.Modulo(left, right); break;
                default:
                    break;
            }
            return opCall;
        }

        internal static Expression UnaryExpression(OperatorToken o, Expression operand)
        {
            Expression opCall = null;
            if (MathParser._defaultOp.Any(c => c.ToString() == o.Value.ToString()))
            {
                switch (o.Value.ToString())
                {
                    case "+":
                        opCall = operand; break;

                    case "-":
                        opCall = Expression.Negate(operand); break;
   
                    case "!":
                        opCall = Expression.Not(operand); break;

                    case "~":
                        opCall = Expression.OnesComplement(operand); break;

                    default:
                        break;
                }
            }
            return opCall;
        }


        internal class ParametersVisitor : System.Linq.Expressions.ExpressionVisitor
        {
            private IList<Expression> parameters;
            private int indexer = 0;
            public ParametersVisitor(IList<Expression> inputParameters)
            {
                parameters = inputParameters;
            }

            public IList<Expression> GetParameters(Expression expression)
            {
                base.Visit(expression);
                return parameters;
            }

            protected override Expression VisitBinary(System.Linq.Expressions.BinaryExpression node)
            {
                return base.VisitBinary(node);
            }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                var paramToReturn = base.VisitParameter(node);
                if (indexer <= parameters.Count - 1)
                {
                    var p = parameters[indexer];
                    if (p.Type != paramToReturn.Type)
                    {
                        p = Expression.Convert(p, paramToReturn.Type);
                        parameters[indexer] = p;
                    }
                    indexer++;
                }
                return paramToReturn;
            }
        }

    }
}
